package com.sondertara.common.lang.reflect;

import com.sondertara.common.exception.TaraException;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * {@link java.lang.invoke.MethodHandles.Lookup}工厂，用于创建{@link java.lang.invoke.MethodHandles.Lookup}对象<br>
 * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link java.lang.invoke.MethodHandles.Lookup}在调用findSpecial和unreflectSpecial
 * 时会出现权限不够问题，抛出"no private access for
 * invokespecial"异常，因此针对JDK8及JDK9+分别封装lookup方法。
 *
 * 参考：
 * <p>
 * <a href=
 * "https://blog.csdn.net/u013202238/article/details/108687086">https://blog.csdn.net/u013202238/article/details/108687086</a>
 * </p>
 *
 * @author huangxiaohu
 * @since 5.7.7
 */
public class LookupFactory {

	private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
			| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;

	private static Constructor<MethodHandles.Lookup> java8LookupConstructor;
	private static Method privateLookupInMethod;

	static {
		// 先查询jdk9 开始提供的java.lang.invoke.MethodHandles.privateLookupIn方法,
		// 如果没有说明是jdk8的版本.(不考虑jdk8以下版本)
		try {
			// noinspection JavaReflectionMemberAccess
			privateLookupInMethod = MethodHandles.class.getMethod("privateLookupIn", Class.class,
					MethodHandles.Lookup.class);
		} catch (NoSuchMethodException ignore) {
			// ignore
		}

		// jdk8
		// 这种方式其实也适用于jdk9及以上的版本,但是上面优先,可以避免 jdk9 反射警告
		if (privateLookupInMethod == null) {
			try {
				java8LookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
				java8LookupConstructor.setAccessible(true);
			} catch (NoSuchMethodException e) {
				// 可能是jdk8 以下版本
				throw new IllegalStateException(
						"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
						e);
			}
		}
	}

	/**
	 * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link java.lang.invoke.MethodHandles.Lookup}在调用findSpecial和unreflectSpecial
	 * 时会出现权限不够问题，抛出"no private access for
	 * invokespecial"异常，因此针对JDK8及JDK9+分别封装lookup方法。
	 *
	 * @param callerClass 被调用的类或接口
	 * @return {@link java.lang.invoke.MethodHandles.Lookup}
	 */
	public static MethodHandles.Lookup lookup(Class<?> callerClass) {
		// 使用反射,因为当前jdk可能不是java9或以上版本
		if (privateLookupInMethod != null) {
			try {
				return (MethodHandles.Lookup) privateLookupInMethod.invoke(MethodHandles.class, callerClass,
						MethodHandles.lookup());
			} catch (IllegalAccessException | InvocationTargetException e) {
				throw new TaraException(e);
			}
		}
		// jdk 8
		try {
			return java8LookupConstructor.newInstance(callerClass, ALLOWED_MODES);
		} catch (Exception e) {
			throw new IllegalStateException("no 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e);
		}
	}
}
